// Hello project main.go
// this project is a test project.It is for learn Go programming.
package main // 代码包声明语句。

// 代码包导入语句。
import (
	"fmt" // 导入代码包fmt。
	"reflect"
	"strconv"
	"sync/atomic"
	"time"
)

// main函数。
func main() {

	// 打印函数调用语句。用于打印输出信息。
	fmt.Println(" Learn Go language .")
	var (
		num1 int
		num2 int
		num3 int
	)
	num1, num2, num3 = 1, 2, 3
	// 打印函数调用语句。用于打印上述三个变量的值。
	fmt.Println(num1, num2, num3)
	const (
		num4 int = 4
		num5 int = 5
	)
	fmt.Println(num5, num4)
	// 变量声明和赋值语句，由关键字var、变量名num、变量类型uint64、特殊标记=，以及值10组成。
	var num uint64 = 65535

	// 短变量声明语句，由变量名size、特殊标记:=，以及值（需要你来填写）组成。
	size := (8)

	// 打印函数调用语句。在这里用于描述一个uint64类型的变量所需占用的比特数。
	// 这里用到了字符串的格式化函数。
	fmt.Printf("类型为 uint64 的整数 %d 需占用的存储空间为 %d 个字节。\n", num, size)

	num1 = 014 // 用“0”作为前缀以表明这是8进制表示法。
	num1 = 0xC // 用“0x”作为前缀以表明这是16进制表示法。
	// 这里用到了字符串格式化函数。其中，%X用于以16进制显示整数类型值，%d用于以10进制显示整数类型值。
	fmt.Printf("16进制数 %X 表示的是 %d。\n", num1, (12))
	/* 可以在变量声明并赋值的语句中，省略变量的类型部分。
	   不过别担心，Go语言可以推导出该变量的类型。*/
	var num22 = 5.89E-4

	// 这里用到了字符串格式化函数。其中，%E用于以带指数部分的表示法显示浮点数类型值，%f用于以通常的方法显示浮点数类型值。
	fmt.Printf("浮点数 %E 表示的是 %f。\n", num22, (0.000589))
	var num33 = 3.7E+1 + 5.98E-2i

	// 这里用到了字符串格式化函数。其中，%E用于以带指数部分的表示法显示浮点数类型值，%f用于以通常的方法显示浮点数类型值。
	fmt.Printf("浮点数 %E 表示的是 %f。\n", num33, (37 + 0.0598i))
	// 声明一个rune类型变量并赋值
	var char1 rune = '赞'

	// 这里用到了字符串格式化函数。其中，%c用于显示rune类型值代表的字符。
	fmt.Printf("字符 '%c' 的Unicode代码点是 %s。\n", char1, ("U+8D5E"))
	r := '我'
	fmt.Printf("%q的类型为：%t 二进制为：%b\n", r, r, r)

	rType := reflect.TypeOf(r).Kind()
	fmt.Printf("r的实际类型为：%s\n", rType)

	// 声明一个string类型变量并赋值
	var str1 string = "\\\""

	// 这里用到了字符串格式化函数。其中，%q用于显示字符串值的表象值并用双引号包裹。
	fmt.Printf("用解释型字符串表示法表示的 %q 所代表的是 %s。\n", str1, `\"`)

	var numbers2 [5]int
	var numbers = [...]int{1, 2, 3}
	numbers2[0] = 2
	numbers2[3] = numbers2[0] - 3
	numbers2[1] = numbers2[2] + 5
	numbers2[4] = len(numbers2)
	fmt.Print(numbers2[4])
	fmt.Print(numbers)
	sum := 11
	// “==”用于两个值的相等性判断
	fmt.Printf("%v\n", (sum == numbers2[0]+numbers2[1]+numbers2[2]+numbers2[3]+numbers2[4]))

	//一个切片值的容量即为它的第一个元素值在其底层数组中的索引值与该数组长度的差值的绝对值。为了获取数组、切片或通道类型的值的容量，我们可以使用内建函数cap
	var numberss3 = [5]int{1, 2, 3, 4, 5}
	var numberss5 = []int{1, 2, 3, 4, 5}
	numberss5[3] = 10
	slice3 := numberss3[2:len(numberss3)]
	length := (3)
	capacity := (3)
	fmt.Printf("%v, %v\n. %d, %d\n", (length == len(slice3)), (capacity == cap(slice3)), numberss5[3], cap(slice3))
	myfun()
	appenddemo()
	dicdemo()
	chandemo()
	chandemo1()
	fundemo()
	structdemo()
	interfacedemo()
	pointerdemo()
	interface1demo()
	interface2demo()
}

func myfun() {
	var numbers4 = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	slice5 := numbers4[4:6:8]
	length := (2)
	capacity := (4)
	fmt.Printf("%v, %v\n", length == len(slice5), capacity == cap(slice5))
	slice5 = append(slice5, 11)
	fmt.Println(slice5)
	fmt.Println(numbers4)
	/*
	   如果新切片的长度未超过源切片的容量，则返回源切片
	   如果追加后的新切片长度超过源切片的容量，则会返回全新的切片
	*/
	slice5 = slice5[:cap(slice5)]
	fmt.Println(slice5)

	slice5 = append(slice5, 11, 12, 13)
	fmt.Println(slice5)
	fmt.Println(numbers4)
	length = (7)
	fmt.Printf("%v\n", length == len(slice5))
	slice6 := []int{0, 0, 0}
	copy(slice5, slice6)
	e2 := (0)
	e3 := (8)
	e4 := (11)
	fmt.Printf("%v, %v, %v\n", e2 == slice5[2], e3 == slice5[3], e4 == slice5[4])
}

func appenddemo() {
	s := []int{1, 2, 3, 4, 5}
	fmt.Printf("s: %p %d %d %v\n", s, len(s), cap(s), s) //s: 0xc04200bfb0 5 5 [1 2 3 4 5]
	fmt.Printf("s: %p %d %d %[1]v\n", s, len(s), cap(s)) //s: 0xc04200bfb0 5 5 [1 2 3 4 5]
	ss := append(s, 6)
	fmt.Printf("ss: %p %d %d %[1]v\n", ss, len(ss), cap(ss)) //ss: 0xc042004320 6 10 [1 2 3 4 5 6]
	ss[0] = 9
	fmt.Printf("s: %p %d %d %[1]v\n", s, len(s), cap(s))     //s: 0xc04200bfb0 5 5 [1 2 3 4 5]
	fmt.Printf("ss: %p %d %d %[1]v\n", ss, len(ss), cap(ss)) //ss: 0xc04205e050 6 10 [9 2 3 4 5 6]
	//*****
	s1 := make([]int, 3, 5)
	fmt.Printf("s1: %p %d %d %[1]v\n", s1, len(s1), cap(s1)) //s1: 0xc042031da0 3 5 [0 0 0]
	ss1 := append(s1, 1)
	fmt.Printf("ss1: %p %d %d %[1]v\n", ss1, len(ss1), cap(ss1)) //ss1: 0xc042031da0 4 5 [0 0 0 1]
	ss1[0] = 7
	fmt.Printf("s1: %p %d %d %[1]v\n", s1, len(s1), cap(s1))     //s1: 0xc042031da0 3 5 [7 0 0]
	fmt.Printf("ss1: %p %d %d %[1]v\n", ss1, len(ss1), cap(ss1)) //ss1: 0xc042031da0 4 5 [7 0 0 1]
	ss1 = append(ss1, s1...)
	fmt.Printf("s1: %p %d %d %[1]v\n", s1, len(s1), cap(s1))     //s1: 0xc042031da0 3 5 [7 0 0]
	fmt.Printf("ss1: %p %d %d %[1]v\n", ss1, len(ss1), cap(ss1)) //ss1: 0xc04205e0a0 7 10 [7 0 0 1 7 0 0]
}

func dicdemo() {
	/*
		字典的键类型必须是可比较的，否则会引起错误。也就是说，它不能是切片、字典或函数类型。
		实际上，字典中的键值对也没有索引，而是与我们要取出的值对应的那个键。
	*/

	mm2 := map[string]int{"golang": 42, "java": 1, "python": 8}
	mm2["scala"] = 25
	mm2["erlang"] = 50
	delete(mm2, "python")
	fmt.Printf("%d, %d, %d \n", mm2["scala"], mm2["erlang"], mm2["python"])
}

func chandemo() {
	ch2 := make(chan string, 1)
	// 下面就是传说中的通过启用一个Goroutine来并发的执行代码块的方法。
	// 关键字 go 后跟的就是需要被并发执行的代码块，它由一个匿名函数代表。
	// 对于 go 关键字以及函数编写方法，我们后面再做专门介绍。
	// 在这里，我们只要知道在花括号中的就是将要被并发执行的代码就可以了。
	go func() {
		ch2 <- ("已到达！")
	}()
	var value string = "数据"
	value = value + (<-ch2)
	fmt.Println(value)
}

type Sender chan<- int
type Receiver <-chan int

func chandemo1() {
	/*
			通道有带缓冲和非缓冲之分。我们已经说过，缓冲通道中可以缓存N个数据。我们在初始化一个通道值的时候必须指定这个N。相对的，非缓冲通道不会缓存任何数据。发送方在向通道值发送数据的时候会立即被阻塞，直到有某一个接收方已从该通道值中接收了这条数据。非缓冲的通道值的初始化方法如下：

		make(chan int, 0)
		    注意，在这里，给予make函数的第二个参数值是0。

		    除了上述分类方法，我们还可以以数据在通道中的传输方向为依据来划分通道。默认情况下，通道都是双向的，即双向通道。如果数据只能在通道中单向传输，那么该通道就被称作单向通道。我们在初始化一个通道值的时候不能指定它为单向。但是，在编写类型声明的时候，我们却是可以这样做的。例如：

		type Receiver <-chan int
		    类型Receiver代表了一个只可从中接收数据的单向通道类型。这样的通道也被称为接收通道。在关键字chan左边的接收操作符<-形象地表示出了数据的流向。相对应的，如果我们想声明一个发送通道类型，那么应该这样：

		type Sender chan<- int
		    这次<-被放在了chan的右边，并且“箭头”直指“通道”。想必不用多说你也能明白了。我们可以把一个双向通道值赋予上述类型的变量，就像这样：

		var myChannel = make(chan int, 3)
		var sender Sender = myChannel
		var receiver Receiver = myChannel
		    但是，反之则是不行的。像下面这样的代码是通不过编译的：

		var myChannel1 chan int = sender
		    单向通道的主要作用是约束程序对通道值的使用方式。比如，我们调用一个函数时给予它一个发送通道作为参数，以此来约束它只能向该通道发送数据。又比如，一个函数将一个接收通道作为结果返回，以此来约束调用该函数的代码只能从这个通道中接收数据。这属于API设计的范畴。因此我们在这里仅了解一下即可。
	*/

	var myChannel = make(chan int, 0)
	var number = 6
	go func() {
		var sender Sender = myChannel
		sender <- number
		fmt.Println("Sent!")
	}()
	go func() {
		var receiver Receiver = myChannel
		fmt.Println("Received!", <-receiver)
	}()
	// 让main函数执行结束的时间延迟1秒，
	// 以使上面两个代码块有机会被执行。
	time.Sleep(time.Second)
}

// 员工ID生成器
type EmployeeIdGenerator func(company string, department string, sn uint32) string

// 默认公司名称
var company = "Gophers"

// 序列号
var sn uint32

// 生成员工ID
func generateId(generator EmployeeIdGenerator, department string) (string, bool) {
	// 这是一条 if 语句，我们会在下一章讲解它。
	// 若员工ID生成器不可用，则无法生成员工ID，应直接返回。
	if generator == nil {
		return "", false
	}
	// 使用代码包 sync/atomic 中提供的原子操作函数可以保证并发安全。
	newSn := atomic.AddUint32(&sn, 1)
	return generator(company, department, newSn), true
}

// 字符串类型和数值类型不可直接拼接，所以提供这样一个函数作为辅助。
func appendSn(firstPart string, sn uint32) string {
	return firstPart + strconv.FormatUint(uint64(sn), 10)
}

func fundemo() {
	var generator EmployeeIdGenerator
	generator = func(company string, department string, sn uint32) string {
		return appendSn(company+"-"+department+"-", sn)
	}
	fmt.Println(generateId(generator, "RD"))
}

func structdemo() {
	/*
			type Person struct {
		    Name   string
		    Gender string
		    Age    uint8

		}
		    结构体类型Person中有三个字段，分别是Name、Gender和Age。我们可以用字面量创建出一个该类型的值，像这样：

		Person{Name: "Robert", Gender: "Male", Age: 33}
			结构体类型可以拥有若干方法（注意，匿名结构体是不可能拥有方法的）。所谓方法，其实就是一种特殊的函数。它可以依附于某个自定义类型。方法的特殊在于它的声明包含了一个接收者声明。这里的接收者指代它所依附的那个类型。我们仍以结构体类型Person为例。下面是依附于它的一个名为Grow的方法的声明：

			func (person *Person) Grow() {
			    person.Age++
			}
			最后，结构体类型属于值类型。它的零值并不是nil，而是其中字段的值均为相应类型的零值的值。举个例子，结构体类型Person的零值若用字面量来表示的话则为Person{}
		实际上，在Go语言中并没有继承的概念。不过，我们可以通过在结构体类型的声明中添加匿名字段（或称嵌入类型）来模仿继承。

	*/
	p := Person{"Robert", "Male", 33, "Beijing"}
	oldAddress := p.Move("San Francisco")
	fmt.Printf("%s moved from %s to %s.\n", p.Name, oldAddress, p.Address)
}

type Person struct {
	Name    string
	Gender  string
	Age     uint8
	Address string
}

func (person *Person) Move(newAddress string) string {
	old := person.Address
	person.Address = newAddress
	return old
}

type Animal interface {
	Grow()
	Move(string) string
}

type Cat struct {
	Name     string
	Age      uint8
	Location string
}

func (cat *Cat) Grow() {
	cat.Age--
}

func (cat *Cat) Move(new string) string {
	return new
}

func interfacedemo() {
	var phone Cat
	phone.Name = "Little C"
	phone.Age = 2
	phone.Location = "In the house"
	animal, ok := interface{}(&phone).(Animal)
	fmt.Printf("%v, %v\n", ok, animal)
}

func pointerdemo() {
	mi := MyInt{}
	mi.Increase()
	mi.Increase()
	mi.Decrease()
	mi.Decrease()
	mi.Increase()
	fmt.Printf("%v\n", mi.n == 1)
}

type MyInt struct {
	n int
}

func (myInt *MyInt) Increase() {
	myInt.n++
}

func (myInt *MyInt) Decrease() {
	myInt.n--
}

/*
一个指针类型拥有以它以及以它的基底类型为接收者类型的所有方法，而它的基底类型却只拥有以它本身为接收者类型的方法
*/

type Pet interface {
	Name() string
	Age() uint8
}

type Dog struct {
	name string
	age  uint8
}

type Dog1 struct {
	name string
	age  uint8
}

func (d Dog) Name() string {
	return d.name
}

func (d Dog) Age() uint8 {
	return d.age
}

func (d Dog1) Name1() string {
	return d.name
}

func (d Dog1) Age() uint8 {
	return d.age
}

func (d *Dog1) Name() string {
	return d.name
}

func interface1demo() {
	myDog := Dog{"Little D", 3}
	_, ok1 := interface{}(&myDog).(Pet) //type myDog *Dog
	_, ok2 := interface{}(myDog).(Pet)  //type myDog Dog
	fmt.Printf("%v, %v\n", ok1, ok2)
}

func interface2demo() {
	myDog1 := Dog1{"Little D", 3}
	_, ok1 := interface{}(&myDog1).(Pet) //type myDog1 *Dog1
	_, ok2 := interface{}(myDog1).(Pet)  //type myDog1 Dog1
	fmt.Printf("%v, %v\n", ok1, ok2)
}
